/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 *
 */

import { intersection, noop } from '@baize/wdk';
import { Input, InputFile, MetricLogType, OutputFile, TaskDetails } from '@ohos/hvigor';
import fs from 'fs';
import fse from 'fs-extra';
import path from 'path';

import { ModulePathInfoIml } from '../../common/iml/module-path-info-iml.js';
import { CommonConst } from '../../const/common-const.js';
import { JsonProfile } from '../../model/json-profile.js';
import { LegacyTargetSourceSetModel, TargetSourceSetModel } from '../../model/source-set/source-set-model.js';
import { getIntersectedSet } from '../../utils/array-util.js';
import { FileUtil } from '../../utils/file-util.js';
import { getJson5Obj } from '../../utils/json-util.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';
import { ProcessUtils } from '../../utils/process-utils.js';
import { PreCheckSyscap } from '../pre-check-syscap.js';
import { TargetTaskService } from '../service/target-task-service.js';
import { OhosHapTask } from '../task/ohos-hap-task.js';

/**
 * Syscap Transform task
 *
 * @since 2022/09/8
 */
export abstract class AbstractSyscapTransform extends OhosHapTask {
  private _log = OhosLogger.getLogger(AbstractSyscapTransform.name);
  protected readonly sourceRoot: string;
  public readonly pathInfo: ModulePathInfoIml;
  @InputFile() protected readonly sysCapJsonPath: string;
  @OutputFile() protected readonly rpcidScPath: string;

  @InputFile() protected get sysCapTool(): string {
    return this.sdkInfo.getSysCapTool();
  }
  @InputFile() protected get sysCapPath(): string {
    return fs.existsSync(this.sdkInfo.getSdkJsDir()) && !fs.existsSync(this.sdkInfo.getSdkEtsDir())
      ? this.sdkInfo.getSysCapFileInJs()
      : this.sdkInfo.getSysCapFileInEts();
  }
  @Input() protected get deviceTypes(): string[]{
    return this.getJsonProfileByModel().deviceTypes;
  }

  protected constructor(taskService: TargetTaskService, taskName: TaskDetails) {
    super(taskService, taskName);
    const targetName = taskService.getTargetData().getTargetName();
    this.sourceRoot = this.service.getModuleModel()?.getSourceRootByTargetName(targetName);
    this.pathInfo = this.targetService.getTargetData().getPathInfo();
    this.sysCapJsonPath = path.resolve(this.sourceRoot, CommonConst.SYSCAP_JSON);
    this.rpcidScPath = path.resolve(this.pathInfo.getIntermediatesSysCap(), CommonConst.RPCID_SC);
  }

  initTaskDepends() {
    this.declareDepends(PreCheckSyscap.name);
  }

  protected async doTaskAction(): Promise<void> {
    if (this.targetData.isHarmonyOS()) {
      return;
    }
    const subTaskName = 'generate SysCap transform command';
    const subDurationEvent = this.durationEvent.createSubEvent(subTaskName, '');
    subDurationEvent.start();

    const jsonProfileByModel: JsonProfile = this.getJsonProfileByModel();
    let finalIntersectedSysCapSet: Set<string> = new Set<string>();
    const deviceTypesList = jsonProfileByModel.deviceTypes;
    const intersectedDeviceSysCapSet = this.getIntersectedGeneralSysCapSet(deviceTypesList);

    if (fse.existsSync(this.sysCapJsonPath)) {
      const sysCapJsonObj: SyscapJsonObj = getJson5Obj(this.sysCapJsonPath);
      const customSysCapList = sysCapJsonObj.devices.custom;
      const intersectedCustomSysCapSet = this.getIntersectedCustomSysCapSet(customSysCapList);

      if (deviceTypesList.length === 0) {
        // 场景1:工程无指定设备类型
        this.checkCustomSysCapList(customSysCapList);
        finalIntersectedSysCapSet = intersectedCustomSysCapSet;
      } else {
        // 场景3:工程仅存在唯一设备类型,通过SDK获得的general中设备类型的SysCap || 场景5:工程存在多设备类型,通过SDK获得的general中多设备类型的SysCap并取交集
        // 若custom数组定义的N设备对象syscap集合存在且不为空,则取custom数组定义的N设备对象syscap集合与场景3/场景5获取的集合取交集后处理
        // 若custom字段不存在或custom数组定义的数组存在但对象为空,则取场景3/场景5获取的集合做处理
        finalIntersectedSysCapSet =
          customSysCapList && customSysCapList.length !== 0
            ? getIntersectedSet(intersectedCustomSysCapSet, intersectedDeviceSysCapSet)
            : intersectedDeviceSysCapSet;
      }

      // 根据production配置做增删
      const productionSysCapObj = sysCapJsonObj.production;
      if (productionSysCapObj) {
        this.processProductionSysCap(productionSysCapObj, finalIntersectedSysCapSet);
      }
    }

    if (!fse.existsSync(this.sysCapJsonPath)) {
      // 场景2:工程仅存在唯一设备类型,同时无导入pcid.sc
      // 场景4:工程中存在多设备类型,同时无导入pcid.sc
      finalIntersectedSysCapSet = intersectedDeviceSysCapSet;
    }

    // har不处理sysCap逻辑
    if (this.moduleModel.isHarModule()) {
      subDurationEvent.stop();
      subDurationEvent.setLog(subTaskName, MetricLogType.INFO);
      return;
    }

    if (!finalIntersectedSysCapSet?.size) {
      const configPath = this.isFaMode
        ? (this.targetData.getModuleSourceSetModel() as LegacyTargetSourceSetModel).getLegacyModuleTargetRes().getJsonPath()
        : (this.targetData.getModuleSourceSetModel() as TargetSourceSetModel).getModuleTargetRes().getJsonPath();
      this._log.printErrorExit(
        'INVALID_CUSTOM_SYSCAP',
        [fse.existsSync(this.sysCapJsonPath) ? this.sysCapJsonPath : configPath],
        [[CommonConst.SYSCAP_JSON, this.moduleName, CommonConst.MODULE_JSON5, CommonConst.CONFIG_JSON]]
      );
    }

    const newSysCapMap = new Map<string, any>();
    newSysCapMap.set(CommonConst.API_VERSION, this.compileApiVersion);
    newSysCapMap.set(CommonConst.SYSTEM_CAPABILITY, Array.from(finalIntersectedSysCapSet));
    const intermediatesSysCapPath = this.pathInfo.getIntermediatesSysCap();
    FileUtil.checkDirWithoutDelete(intermediatesSysCapPath);
    const rpcidJsonPath = path.resolve(intermediatesSysCapPath, CommonConst.RPCID_JSON);
    fse.outputJsonSync(rpcidJsonPath, Object.fromEntries(newSysCapMap));

    // syscap转换:rpcid.json -----> rpcid.sc
    const inputData = {
      moduleName: this.moduleName,
      taskName: this.name,
      commandLine: [this.sysCapTool, '-R', '-e', '-i', rpcidJsonPath, '-o', intermediatesSysCapPath],
    };

    subDurationEvent.stop();
    subDurationEvent.setLog(subTaskName, MetricLogType.INFO);

    const subToolTaskName = 'submit SysCap transform task to work pool';
    const subToolDurationEvent = this.durationEvent.createSubEvent(subToolTaskName, '');
    subToolDurationEvent.start();

    await new ProcessUtils(inputData.moduleName, inputData.taskName).submitExecutionWithoutReturn(
      this,
      this.getWorkerPool(),
      inputData,
      noop,
      [],
      subToolDurationEvent,
    );

    subToolDurationEvent.stop();
    subToolDurationEvent.setLog(subToolTaskName, MetricLogType.INFO);
  }

  // 通过模型获取对应json配置文件
  protected abstract getJsonProfileByModel(): JsonProfile;

  /**
   * 获取general中定义的设备类型集合其对应设备定义的syscap能力集的交集
   * @param deviceTypesList
   * @protected
   */
  protected getIntersectedGeneralSysCapSet(deviceTypesList: string[]): Set<string> {
    if (!deviceTypesList?.length) {
      return new Set<string>();
    }

    if (deviceTypesList.length === 1) {
      return this.getSysCapSetFromSdk(deviceTypesList[0], this.sysCapPath) ?? new Set<string>();
    }

    const intersectedList = Array.from(
      deviceTypesList
        .map((deviceType) => Array.from(this.getSysCapSetFromSdk(deviceType, this.sysCapPath) ?? new Set<string>()))
        .reduce((sysCapList1, sysCapList2) => sysCapList1.filter((sysCapList3) => sysCapList2.includes(sysCapList3))),
    );
    return new Set(intersectedList);
  }

  /**
   * 根据设备类型从sdk中获取其对应的syscap能力集
   * 如果设备无syscap定义json文件则返回undefined
   * @param deviceType
   * @param deviceDefinePath
   * @protected
   */
  protected getSysCapSetFromSdk(deviceType: string, deviceDefinePath: string): Set<string> | undefined {
    const deviceSysCapPath = path.resolve(deviceDefinePath, `${deviceType}.json`);
    if (!fse.existsSync(deviceSysCapPath)) {
      this._log.warn(`The systemCapability set corresponding to the '${deviceType}' is not found in the '${deviceDefinePath}'.`);
      return undefined;
    }

    const deviceSysCapSet = new Set<string>();
    const deviceSysCapObj = getJson5Obj(deviceSysCapPath);
    deviceSysCapObj['SysCaps'].forEach((syscap: string) => {
      deviceSysCapSet.add(syscap);
    });
    return deviceSysCapSet;
  }

  /**
   * 获得syscap.json中custom定义的N设备类型其对应的syscap能力集交集
   * @param customSysCapList NDeviceType中SysCap
   */
  protected getIntersectedCustomSysCapSet(customSysCapList: object[] | undefined): Set<string> {
    if (!customSysCapList?.length || customSysCapList.some((customSysCap) => JSON.stringify(customSysCap) === '{}')) {
      return new Set<string>();
    }

    const newCustomSysCapList: string[][] = [];
    customSysCapList.forEach((key: any) => {
      Object.keys(key).forEach((value) => {
        newCustomSysCapList.push(key[value]);
      });
    });

    return new Set<string>(intersection(...newCustomSysCapList));
  }

  /**
   * 根据production配置对sysCap交集进行增删处理
   * @param productionObj
   * @param intersectedSysCapSet
   * @protected
   */
  protected processProductionSysCap(productionObj: ProductionObj, intersectedSysCapSet: Set<string>): void {
    const addedSysCaps = productionObj.addedSysCaps;
    if (addedSysCaps) {
      addedSysCaps.forEach((addedSysCap) => {
        intersectedSysCapSet.add(addedSysCap);
      });
    }

    const removedSysCaps = productionObj.removedSysCaps;
    if (removedSysCaps) {
      removedSysCaps.forEach((removedSysCap) => {
        intersectedSysCapSet.delete(removedSysCap);
      });
    }
  }

  /**
   * 处理custom字段异常场景
   * 1.custom字段不存在/custom字段存在但数组为空没有对象则不处理custom
   * 2.custom存在 数组不空但是对象为空则按空集报错处理
   * @param customSysCapList
   * @private
   */
  private checkCustomSysCapList(customSysCapList: object[] | undefined): void {
    if (!customSysCapList || customSysCapList.length === 0) {
      this._log.printErrorExit(
        'CUSTOM_SYSCAP_IN_EMPTY_DEVICE_TYPES',
        [CommonConst.MODULE_JSON5, this.moduleName, CommonConst.SYSCAP_JSON, this.sysCapJsonPath],
        [[CommonConst.MODULE_JSON5, CommonConst.SYSCAP_JSON, this.moduleName]]
      );
    }
  }
}

/**
 * syscap.json对象配置
 */
export interface SyscapJsonObj {
  devices: SyscapDeviceObj;
  production: ProductionObj;
  development: DevelopmentObj;
}

export interface SyscapDeviceObj {
  general: string[];
  custom: object[];
}

export interface ProductionObj {
  addedSysCaps: string[];
  removedSysCaps: string[];
}

export interface DevelopmentObj {
  addedSysCaps: string[];
}
